﻿using TribesPSO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;

namespace TribesPSOTest
{


    /// <summary>
    ///This is a test class for ParticleTest and is intended
    ///to contain all ParticleTest Unit Tests
    ///</summary>
    [TestClass()]
    public abstract class ParticleTest
    {


        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        protected abstract Particle CreateParticle();
        protected abstract Particle CreateParticle(EuclidianVector position, IObjectiveFunction function);

        protected Particle_Accessor CreateParticle_Accessor()
        {
            PrivateObject obj = new PrivateObject(CreateParticle(), new PrivateType(typeof(Particle)));
            return new Particle_Accessor(obj);
        }
        protected Particle_Accessor CreateParticle_Accessor(EuclidianVector position, IObjectiveFunction function)
        {
            PrivateObject obj = new PrivateObject(CreateParticle(position, function), new PrivateType(typeof(Particle)));
            return new Particle_Accessor(obj);
        }

        /// <summary>
        ///A test for MemorizePerformance
        ///</summary>
        [TestMethod()]
        public void MemorizePerformanceTest()
        {
            Particle_Accessor target = CreateParticle_Accessor();
            target.MemorizePerformance(false);
            target.MemorizePerformance(false);
            Assert.AreEqual(false, target.IsGood);
            Assert.AreEqual(false, target.IsExcellent);
            target.MemorizePerformance(true);
            target.MemorizePerformance(false);
            Assert.AreEqual(false, target.IsGood);
            Assert.AreEqual(false, target.IsExcellent);
        }

        /// <summary>
        ///A test for MemorizePerformance
        ///</summary>
        [TestMethod()]
        public void MemorizeGoodPerformanceTest()
        {
            Particle_Accessor target = CreateParticle_Accessor();
            target.MemorizePerformance(false);
            target.MemorizePerformance(true);
            Assert.AreEqual(true, target.IsGood);
            Assert.AreEqual(false, target.IsExcellent);
            target.MemorizePerformance(true);
            target.MemorizePerformance(true);
            Assert.AreEqual(true, target.IsGood);
            Assert.AreEqual(true, target.IsExcellent);
        }

        /// <summary>
        ///A test for Parent
        ///</summary>
        [TestMethod()]
        public void ParentTest()
        {
            Particle target = CreateParticle();
            Tribe expected = null;
            Tribe actual;
            actual = target.Parent;
            Assert.AreEqual(expected, actual);

            //Now add the parent to a tribe
            Tribe parentTribe = new Tribe(target);
            Assert.AreEqual(parentTribe, target.Parent);

            //Finally make sure we can't change the parent
            try
            {
                Tribe imposterTribe = new Tribe(target);
                Assert.Fail("Expected: InvalidOperationException.  Got no exception");
            }
            catch (InvalidOperationException)
            {
                //Good, we got the exception we were looking for
            }
            catch (Exception e)
            {
                Assert.Fail("Expected: InvalidOperationException.  Got: {0}", e.GetType());
            }
        }

        /// <summary>
        ///A test for Parent
        ///</summary>
        [TestMethod()]
        public void WrongParentTest()
        {
            Particle target = CreateParticle();
            Particle dummy = CreateParticle();

            Tribe WrongTribe = new Tribe(dummy);

            try
            {
                //This should throw because the target is not actually a member of the tribe we're trying
                //to assign here.
                target.Parent = WrongTribe;
                Assert.Fail("Expected: InvalidOperationException.  Got no exception");
            }
            catch (InvalidOperationException)
            {
                //Good, we got the right exception
            }
            catch (Exception e)
            {
                Assert.Fail("Expected: InvalidOperationException.  Got: {0}", e.GetType());
            }
        }

        /// <summary>
        /// A test for the particle constructor when a null objective function is passed
        /// </summary>
        [TestMethod(), ExpectedException(typeof(ArgumentNullException))]
        public void NullOvjectiveFunctionCtorTest()
        {
            Particle p = CreateParticle(new EuclidianVector(0, 0), null);
        }

        /// <summary>
        /// A test for the particle constructor when a null euclidian vector is passed
        /// </summary>
        [TestMethod(), ExpectedException(typeof(ArgumentNullException))]
        public void NullVectorCtorTest()
        {
            Particle p = CreateParticle(null, new Rosenbrock());
        }

        /// <summary>
        /// A test for the particle constructor when a null euclidian vector is passed
        /// </summary>
        [TestMethod(), ExpectedException(typeof(DimensionMismatchException))]
        public void DimensionMismatchCtorTest()
        {
            //Rosenbrock is a 2d function, but we're going to pass in a 3d initial position
            Particle p = CreateParticle(new EuclidianVector(0, 0, 0), new Rosenbrock());
        }

        /// <summary>
        /// Tests that a particle, on average, will move towards a better solution
        /// </summary>
        [TestMethod()]
        public void MoveTest()
        {
            List<bool> movedCloser = new List<bool>();
            HyperspaceRandom rng = new HyperspaceRandom();
            for (int n = 0; n < 100000; n++)
            {
                movedCloser.Add(RosenbrockMoveTest(rng, 50));
            }

            int goodMoves = movedCloser.Count(o => o);
            int badMoves = movedCloser.Count(o => !o);

            Assert.IsTrue(goodMoves > badMoves, "Failed with {0} good moves and {1} bad moves. Type:{2}", goodMoves, badMoves, this.GetType());
        }

        /// <summary>
        /// Creates a particle at the optimal solution and a particle somewhere within the specified radius.
        /// The non-optimal particle is moved and we check if it got closer to the optimial solution or farther away
        /// </summary>
        /// <param name="rng"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        private bool RosenbrockMoveTest(HyperspaceRandom rng, int radius)
        {
            EuclidianVector bestSolution = new EuclidianVector(1, 1);
            IObjectiveFunction rosenbrock = new Rosenbrock();
            Particle goodParticle = CreateParticle(bestSolution, rosenbrock);
            Particle moveCandidate = CreateParticle(rng.NextUniformVector(bestSolution, radius), rosenbrock);

            double initialDistance = (moveCandidate.Position - bestSolution).Length;

            //Now, we add the two particles to a tribe so they can inform one-another
            Tribe tribe = new Tribe(new Particle[] { goodParticle, moveCandidate });

            moveCandidate.Move();

            double finalDistance = (moveCandidate.Position - bestSolution).Length;

            return finalDistance < initialDistance;
        }
    }
}
